Skip to content

feat(jira): include Jira attachments and recent comments in task context (#577)#619

Merged
ayushtr-aws merged 7 commits into
aws-samples:mainfrom
ayushtr-aws:feat/577-jira-attachments-comments
Jul 21, 2026
Merged

feat(jira): include Jira attachments and recent comments in task context (#577)#619
ayushtr-aws merged 7 commits into
aws-samples:mainfrom
ayushtr-aws:feat/577-jira-attachments-comments

Conversation

@ayushtr-aws

Copy link
Copy Markdown
Contributor

What

Closes #577. Jira-origin tasks now receive the same practical issue context Linear-origin tasks can use — attached files and recent comments, not just the summary/description. Because the Atlassian Remote MCP can't run headlessly (see the parent tracker #580 design note), the context is fetched authenticated at task-admission time in the webhook processor rather than on demand by the agent.

How

  • cdk/src/handlers/shared/jira-attachments.ts (new) —
    • downloadScreenAndStoreJiraAttachments: resolves Jira media attachments via the gateway attachment-content endpoint (api.atlassian.com/ex/jira/{cloudId}/rest/api/3/attachment/content/{id} — the only host the 3LO token is valid against), refresh-and-retry-once on 401/403, enforces the size cap while streaming, validates magic bytes, screens each through the existing Bedrock Guardrail pipeline, uploads cleaned bytes to S3, and returns passed AttachmentRecords. Fail-closed: a selected attachment that can't be safely fetched/screened throws and the task is rejected with a Jira comment. Unsupported/oversized/over-cap attachments are silently skipped. Filenames sanitized + ids validated to prevent S3-key / on-disk path traversal.
    • fetchRecentHumanComments: recent human (accountType: atlassian) comments, ADF→markdown, oldest-first. Fail-open: any failure proceeds without them.
  • cdk/src/handlers/shared/jira-adf.ts (new) — extracted the ADF→markdown walker so the processor and comment helper share it without a circular import (behavior-preserving).
  • cdk/src/handlers/shared/create-task-core.tsTaskCreationContext gains optional taskId (so processor-uploaded S3 keys match the eventual record) and preScreenedAttachments (merged verbatim, never re-screened; a non-passed record fails closed).
  • cdk/src/handlers/jira-webhook-processor.ts — wires both in; comments fold under a ## Recent comments heading, bounded so they never push task_description past the 10k limit (advisory context must not become a hard gate).
  • cdk/src/constructs/jira-integration.ts + agent.ts — pass the attachments bucket to the processor, grant ReadWrite + ATTACHMENTS_BUCKET_NAME, bump the async processor timeout to 60s for serial download+screen. No new OAuth scopes (read:jira-work covers reading attachments + comments).
  • cdk/src/constructs/task-api.ts — exempt /v1/jira/webhook from the WAF SizeRestrictions_BODY managed rule (mirrors the existing Linear/GitHub webhook exemptions). Jira issue payloads carrying attachment metadata exceed the 8 KB body limit and were being blocked at the edge (403) before the receiver Lambda ran — so this is required for feat(jira): include Jira attachments and recent comments in task context #577 to function. The receiver still HMAC-verifies the raw body and the rate-limit rule still applies.

Docs

docs/guides/JIRA_SETUP_GUIDE.md gets an "Issue context: attachments and comments" section (supported types, limits, skip-vs-reject behavior, comment behavior) + regenerated Starlight mirror.

Tests

  • jira-attachments.test.ts (new): download/filter/screen/upload, filename sanitization + unsafe-id rejection, 401 refresh-and-retry, size cap, magic-byte reject, screen-blocked → throw, comment human/app filtering + ADF→markdown + fail-open.
  • jira-webhook-processor.test.ts: comment folding + truncation, fail-closed attachment rejection, remaining-slots math, no-attachments path.
  • create-task-core.test.ts: preScreenedAttachments merge without re-screening, caller-supplied taskId, non-passed fail-closed.
  • task-api.test.ts: WAF exemption present in both the exclusion scope-down and the full-CRS scope-down.

mise run build green (cdk 2357 tests, cli 605, agent + docs pass, synth clean).

Validated end-to-end

Deployed to a dev stack and triggered from a real Jira Cloud tenant: a SCRUM issue with 2 image attachments + a comment triggered a task → comments fetched → both attachments authenticated-downloaded, Bedrock-screened, and stored to S3 → agent hydrated them → committed the images → opened a PR. Confirmed the full path works.

Out of scope

Atlassian Remote MCP support; Confluence/project documents; fetching full historical comment threads; new attachment limits or file types beyond what ABCA already screens.

…ext (aws-samples#577)

Jira-origin tasks previously saw only the issue summary + description (ADF→
markdown) plus embedded HTTPS image URLs. Jira-hosted `media` file attachments
and issue comments were invisible to the agent, so a ticket that says "see the
attached log" or carries acceptance clarifications in comments would run
under-informed. Bring Jira to parity with Linear's on-demand context reads by
fetching the context authenticated at task-admission time in the webhook
processor (the Atlassian Remote MCP can't run headlessly — see aws-samples#580).

- agent-admission download path (cdk/src/handlers/shared/jira-attachments.ts):
  * downloadScreenAndStoreJiraAttachments — resolve Jira `media` attachments via
    the gateway attachment-content endpoint (api.atlassian.com/ex/jira/{cloudId},
    the only host the 3LO token is valid against), refresh-and-retry-once on
    401/403, enforce the size cap while streaming, validate magic bytes, screen
    through the existing Bedrock Guardrail, upload cleaned bytes to S3, and
    return `passed` AttachmentRecords. Fail-closed: a selected attachment that
    can't be safely fetched/screened throws JiraAttachmentError and the task is
    rejected with a Jira comment. Unsupported/oversized/over-cap attachments are
    silently skipped. Filenames are sanitized and ids validated to a safe token
    so neither can traverse the S3 key / agent on-disk path.
  * fetchRecentHumanComments — recent human (accountType `atlassian`) comments,
    ADF→markdown, oldest-first. Fail-open: any failure proceeds without them.
- create-task-core: TaskCreationContext gains optional `taskId` (so processor-
  uploaded S3 keys match the eventual record) and `preScreenedAttachments`
  (merged verbatim, never re-screened; a non-`passed` record fails closed).
- jira-webhook-processor: wire both in; comments fold under a "## Recent
  comments" heading, bounded so they never push task_description past the 10k
  limit (advisory context must not become a hard gate).
- jira-adf.ts: extract the ADF→markdown walker so the processor and the comment
  helper share it without a circular import (behavior-preserving).
- CDK: pass the attachments bucket to JiraIntegration; grant the processor
  ReadWrite + ATTACHMENTS_BUCKET_NAME env; bump the async processor timeout to
  60s for serial download+screen. No new OAuth scopes (read:jira-work covers it).
- docs: JIRA_SETUP_GUIDE "Issue context: attachments and comments" (supported
  types, limits, skip vs fail-closed reject, comment behavior) + Starlight mirror.

Tests: new jira-attachments.test.ts (download/filter/screen/upload, sanitize,
401 retry, size cap, comment human/app filter + fail-open); processor tests for
comment folding + truncation and fail-closed attachment rejection; create-task-
core tests for preScreenedAttachments merge + taskId. `mise run build` green.
…s-samples#577)

Jira issue webhook payloads that carry attachment metadata exceed the AWS
managed `SizeRestrictions_BODY` 8 KB limit, so WAF returned 403 at the edge and
the webhook receiver Lambda never ran — the delivery vanished before ABCA saw
it. This is exactly the case aws-samples#577 depends on (an issue with file attachments),
so without this exemption the feature can't work in any WAF-protected deploy.

Mirror the existing `/v1/linear/webhook` and `/v1/github/webhook` handling —
both rules must be updated together:
- AWSManagedRulesCommonRuleSet-TaskPaths (excludes SizeRestrictions_BODY): add
  `/v1/jira/webhook` to the orStatement scope-down so large Jira bodies pass.
- AWSManagedRulesCommonRuleSet (full CRS for other paths): add a NOT
  `/v1/jira/webhook` clause so the path isn't re-covered by the strict rule.

The receiver still HMAC-verifies the raw body and the priority-4 rate-limit
rule still applies, so relaxing the body-size check on this path is safe.

Test: task-api.test.ts asserts the exclusion scope-down contains the Jira path
and the full-CRS scope-down excludes it. 42 pass.
…amples#577)

The Jira attachment-content endpoint
(`/rest/api/3/attachment/content/{id}`) serves the file's own media type and
responds 406 Not Acceptable to a narrow `Accept: application/octet-stream`.
That made every attachment download fail with HTTP 406, and the fail-closed
path then rejected the whole task ("could not be downloaded (HTTP 406)").
Send `Accept: */*` so the gateway serves the real content type.

Verified against the live endpoint: octet-stream → 406, */* → 200.
Test asserts the download request sets Accept: */*.
@ayushtr-aws
ayushtr-aws requested review from a team as code owners July 16, 2026 02:15
@codecov-commenter

codecov-commenter commented Jul 16, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 93.21739% with 78 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@5ec6e14). Learn more about missing BASE report.

Files with missing lines Patch % Lines
cdk/src/handlers/shared/jira-attachments.ts 93.57% 42 Missing ⚠️
cdk/src/handlers/jira-webhook-processor.ts 91.30% 20 Missing ⚠️
cdk/src/handlers/shared/jira-adf.ts 89.80% 16 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #619   +/-   ##
=======================================
  Coverage        ?   89.43%           
=======================================
  Files           ?      228           
  Lines           ?    55898           
  Branches        ?     5668           
=======================================
  Hits            ?    49990           
  Misses          ?     5908           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@isadeks isadeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — Jira attachments + comments (#577)

Strong PR: correct reuse of the screening pipeline (incl. PDF), deliberate fail-closed/fail-open split, thorough path-traversal defenses, 181/181 tests green. Reviewed by reading + running the code (three independent adversarial passes, each finding re-verified). Below is everything actionable, ranked. The design is sound — these are edges, not a rework.

Verified non-issues (checked, no action): SSRF/bearer-token leak on redirect (undici drops Authorization cross-origin; host is a hardcoded constant), path traversal via id/filename, WAF exemption (HMAC verified before processing, rate-limit still covers the path, 10 MB API-GW ceiling), remainingSlots clamp, comment-budget off-by-one (lands exactly at 10k chars), non-passed fail-closed guard, magic-byte validation on real bytes.


Fix before merge

  • 1. Empty attachment crashes the processor with no user feedbackjira-attachments.ts:448types.ts:~630. A 0-byte text/plain/.log/.csv (ordinary user mistake) passes magic-bytes + screening → size_bytes: 0createAttachmentRecord's !params.size_bytes guard throws a plain Error (not JiraAttachmentError), outside the fail-closed conversion. The processor catches only JiraAttachmentError (jira-webhook-processor.ts:232), so the handler throws → no task, no ❌ comment (breaks the fail-closed contract), 2 async retries re-screen everything. Reproduced 3×.
    Fix: in createAttachmentRecord guard params.size_bytes === undefined instead of falsy (same for the size check), OR reject a zero-byte body as JiraAttachmentError before the factory. Add a size_bytes: 0 regression test.

  • 2. 50 MB total-attachment cap trusts attacker metadatajira-attachments.ts:229. selectAttachments sums the running total from payload att.size; the per-file 10 MB cap is stream-enforced but the total is never re-checked against real bytes. size: 1 (or omitted) on 10 files → all pass → up to 100 MB actually downloaded/screened/stored vs the documented 50 MB (cost/limit-integrity, not memory — buffers are loop-local). Confirmed by running: ~10-byte declared batch pulled 60 MB.
    Fix: track a real-bytes running total in the download loop; throw/stop once cumulative bytes exceed MAX_TOTAL_ATTACHMENT_SIZE_BYTES.

  • 3. S3 objects orphaned on any failure after uploadjira-attachments.ts has no cleanup, and createTaskCore's cleanupOrphanedAttachments only tracks inline uploads in uploadedS3Keys — the pre-screened Jira keys are never added. So a mid-batch throw, the non-passed 500 guard, and a non-201 return (e.g. #4 below) all leave uploaded objects behind (90-day lifecycle is the only backstop). Diverges from the cleanup pattern this same PR relies on.
    Fix: track uploaded keys in the download loop and delete them on throw; and/or have the processor delete the returned preScreenedAttachments keys when createTaskCore returns non-201. Add a test asserting no orphan on a mid-batch failure.

Decision needed

  • 4. A policy-tripping human COMMENT fails the whole task closedjira-webhook-processor.ts:466 folds comments into task_description, which createTaskCore guardrail-screens as one blob (create-task-core.ts:387); GUARDRAIL_INTERVENED → 400 → task rejected. The PR's length-truncation budget keeps comments from gating on length, but content-gating comes back through the filter — a third-party comment with a PII-shaped string ("SSN 123-45-6789") or injection-like phrasing fails the reporter's task for text they didn't write. Proven end-to-end. (Description-screening pre-exists on main; #577's new part is routing 3rd-party comment content into it.)
    Options: screen the comment section separately and drop it on intervention (fail-open, matching the comment design), rather than letting it gate task creation — or accept + document the behavior.

Follow-up (robustness)

  • 5. Async retries create duplicate tasks + duplicate attachment work — fresh taskId = ulid() per invocation + no idempotencyKey (jira-webhook-processor.ts:407,479) means attribute_not_exists(task_id) can't dedupe a redelivery. Costlier now (up to 10 re-downloads/screens/uploads per duplicate). Consider a deterministic idempotency key (e.g. issueKey#webhookTimestamp, matching the receiver's dedup key).
  • 6. 60s timeout is optimistic for 10 serial attachments — 10 × 10s fetch timeouts alone exceed 60s; a mid-loop kill orphans objects + triggers the #5 retry. Consider parallelizing the download/screen loop or raising the ceiling.

(This consolidates my two earlier comments on this PR into one list.)

@isadeks isadeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(consolidated into the single review comment above — see it for the actionable list.)

… cap, comment screening, idempotency (aws-samples#577)

Addresses isadeks's review on PR aws-samples#619:

1. Zero-byte attachment → JiraAttachmentError (fail-closed). A 0-byte body
   passed magic-byte + screening then tripped createAttachmentRecord's size
   guard with a plain Error outside the fail-closed conversion (no ❌ comment).
   Now rejected explicitly with a Jira comment.
2. 50 MB total-attachment cap enforced on REAL downloaded bytes, not the
   attacker-declared `size` metadata (which can under-report). Cumulative real
   bytes tracked in the download loop; throws once over the ceiling.
3. S3 orphan cleanup. downloadScreenAndStoreJiraAttachments now tracks uploaded
   keys and best-effort-deletes them if the batch throws mid-way; the processor
   deletes the returned pre-screened objects when createTaskCore returns non-201
   (and on a 200 idempotent replay, whose objects the replayed task doesn't
   reference). New exported cleanupPreScreenedAttachments helper.
4. Comment content screened SEPARATELY and dropped fail-open. Third-party
   comment text that trips the guardrail no longer fails the reporter's task —
   screenCommentsOrDrop screens the folded comment block and drops it (task
   proceeds) on GUARDRAIL_INTERVENED / screening outage / no guardrail.
5. Deterministic idempotency key (`jira-<issueKey>-<webhookTimestamp>`) so an
   async webhook re-delivery dedupes instead of minting a duplicate task and
   re-downloading every attachment. A 200 idempotent replay is handled as
   success (no ❌ comment), cleaning up the round's re-uploaded objects.
6. Webhook-processor timeout 60s → 300s. 10 serial 10s attachment fetches can
   sum past 60s; a mid-loop kill would orphan objects and force a retry.

Tests: +5 (zero-byte reject, real-byte total cap, mid-batch orphan cleanup,
comment-blocked fail-open drop, deterministic idempotency key + 200-replay).
mise run build green (cdk 2362).
@ayushtr-aws

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @isadeks — all six addressed in the latest push. Summary:

Fix before merge

  1. Empty attachment — a 0-byte body is now rejected as JiraAttachmentError before createAttachmentRecord, so it routes through the fail-closed reject-with-comment path instead of throwing a plain Error outside the conversion. Regression test added (size_bytes: 0).
  2. 50 MB total cap — now enforced on real downloaded bytes (cumulative, in the download loop), not the attacker-declared size metadata. Test added with under-declared sizes (6×9 MB real vs size: 1 → throws).
  3. S3 orphan cleanupdownloadScreenAndStoreJiraAttachments tracks uploaded keys and best-effort-deletes them on a mid-batch throw; the processor deletes the returned pre-screened objects when createTaskCore returns non-201 (new exported cleanupPreScreenedAttachments). Mid-batch no-orphan test added.

Decision needed
4. Policy-tripping comment fails the task — went with the fail-open option: comments are now screened separately (screenCommentsOrDrop) and dropped (task proceeds) on GUARDRAIL_INTERVENED / screening outage / no guardrail, rather than being folded into the description blob that gates task creation. So third-party comment content can no longer fail the reporter's task. Test added (blocked comment → dropped, task still created).

Follow-up
5. Duplicate tasks on retry — added a deterministic idempotencyKey (jira-<issueKey>-<webhookTimestamp>, matching the receiver's dedup-key shape) so an async re-delivery dedupes. A 200 idempotent-replay from createTaskCore is now handled as success (no ❌ comment) and cleans up the round's re-uploaded objects.
6. 60s timeout — raised to 300s (10 serial 10 s fetches can sum past 60s; a mid-loop kill would orphan objects + force the #5 retry). Noted parallelizing the loop as a future optimization to lower it again.

Verified non-issues confirmed — thanks for checking those. mise run build green (cdk 2383 after merging latest main).

@isadeks isadeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of 1a742144 — all six addressed, verified by running

Checked out the head and re-ran my original repro scenarios against the fixed code (not just the new tests). Touched suites 144/144 green, tsc clean. All six confirmed:

Fix before merge

  1. Empty attachment ✓ — a 0-byte body now throws JiraAttachmentError before createAttachmentRecord (jira-attachments.ts:406), so it routes through fail-closed reject-with-comment. Re-ran my probe: threw=JiraAttachmentError isJira=true (was a plain Error crashing the handler).
  2. Real-byte total cap ✓ — cumulative totalBytes checked against MAX_TOTAL_ATTACHMENT_SIZE_BYTES on downloaded bytes (:395), not declared size. Re-ran the under-declared batch (6×9 MB real, size:1 each): throws after 5 uploads (45 MB, before the 6th crosses 50 MB) — the 60 MB leak is closed.
  3. S3 orphan cleanup ✓ — loop tracks uploadedKeys, best-effort deleteS3Objects on throw (:496-501); processor calls the new exported cleanupPreScreenedAttachments on both the non-201 path and the 200 idempotent-replay path. Re-ran the mid-batch-block probe: earlier upload attachments/u/t/a0/f0.log is deleted (was orphaned).

Decision needed
4. Comment gating ✓ — went fail-open: screenCommentsOrDrop screens the comment section separately and returns [] (drops, task proceeds) on GUARDRAIL_INTERVENED, no-guardrail, or screening outage. Third-party comment content can no longer fail the reporter's task. (Coarse — one blocked line drops all comments — but that's the safe/advisory choice, not a defect.)

Follow-up
5. Duplicate-task retries ✓ — deterministic buildIdempotencyKey('jira-<issueKey>-<timestamp>'), sanitized to the valid charset + length-capped; I confirmed realistic keys (jira-KAN-42-…) pass isValidIdempotencyKey, so no risk of a malformed key 400-ing every Jira task. 200 replay handled as success (no ❌ comment) with cleanup.
6. Timeout ✓ — 30s→300s, with a note to parallelize the loop later.

Verified non-issues from my earlier review (SSRF/token-leak, path traversal, WAF exemption) still hold.

One thing NOT caused by this PR — but it's a required check and currently red

Secrets, deps, and workflow scan is failing, but it's osv-scanner flagging pre-existing transitive-dep vulns (astro 7.0.5, brace-expansion 1.1.15) in yarn.lock. This PR doesn't touch yarn.lock/package.json, and those advisories are already present on main — so it's dep-drift the scan newly caught, not from #577. Since it's a required merge gate, it'll block any open PR until someone bumps those deps (org-level chore). Flagging so it's not mistaken for a #577 regression and so the merge isn't held on Ayush to fix.

Everything I raised is genuinely fixed. LGTM once the dep-scan (unrelated) is sorted org-side.

@ayushtr-aws
ayushtr-aws added this pull request to the merge queue Jul 21, 2026
Merged via the queue into aws-samples:main with commit beef46a Jul 21, 2026
4 checks passed
@ayushtr-aws
ayushtr-aws deleted the feat/577-jira-attachments-comments branch July 21, 2026 17:28
isadeks added a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 21, 2026
…tern, not confirm-uploads

Verified: PR aws-samples#619 (merged) already solved headless vendor attachments for Jira
by fetching them AUTHENTICATED at task-admission time in the webhook processor
(jira-attachments.ts) and injecting via create-task-core's preScreenedAttachments
seam — bypassing the confirm-uploads/Cognito flow entirely. Also verified the
confirm-uploads endpoint IS Cognito-only (task-api.ts:834), which is why the
presigned/large-file path is unavailable to webhook callers — but aws-samples#619 shows
that path is not needed.

So the fix is NOT adding HMAC auth to confirm-uploads; it's building the Linear
analog linear-attachments.ts (fetch uploads.linear.app + paperclip attachments
with the @bgagent ChannelCredential, screen via Bedrock Guardrail, inject as
preScreenedAttachments), mirroring aws-samples#619. P4 updated to reference aws-samples#619 as the
template and to note the confirm-uploads limitation is irrelevant to this path.
This confirms "remove Linear MCP, do attachments deterministically" — the
maintainer's short-term "use Linear MCP" note predates having aws-samples#619 to copy.
isadeks added a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 22, 2026
…aws-samples#619) [P4.1]

ADR-016 P4.1: Linear runs deterministically (no MCP), so the agent can't fetch
uploads.linear.app images at runtime. New cdk/src/handlers/shared/linear-attachments.ts
mirrors jira-attachments.ts: at webhook admission, fetch uploads.linear.app
images (extracted from the issue description markdown) AUTHENTICATED with the
workspace @bgagent OAuth bearer, validate magic bytes, screen through the same
Bedrock Guardrail pipeline, upload cleaned bytes to S3, and return passed
AttachmentRecords injected via createTaskCore's preScreenedAttachments seam.
Fail-closed (LinearAttachmentError → reject task with a comment); SSRF-guarded
(HTTPS-only, uploads-host-only, private-IP rejection); per-file + total + count
caps on real bytes; batch cleanup of uploaded objects on any mid-batch failure.

Wired into linear-webhook-processor single-task path (mints taskId up-front,
fail-closed when ATTACHMENTS_BUCKET/GUARDRAIL unconfigured, cleanup on non-201).
Public-CDN markdown images stay on the existing unauthenticated URL path; only
uploads.linear.app comes through the authenticated path.

+17 unit tests (linear-attachments.test.ts, 88.76% stmt cov). Updated the 2
webhook-processor tests that encoded the retired "skip uploads.linear.app"
behavior to the new fetch/fail-closed contract. CDK build green: 163 suites/3230.
isadeks added a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 22, 2026
Extend linear-attachments.ts beyond PNG/JPEG to the full platform
allowlist (PDF, text, csv, markdown, json, log). Linear attaches uploaded
files as plain [label](uploads.linear.app/…) links (not inline images), so
the description scan now matches both the image ![]() and link []() forms.
MIME is resolved content-type → extension → magic-bytes (incl %PDF-), and
non-image types are screened via screenTextFile, mirroring jira-attachments
(aws-samples#619). Unsupported types (docx, zip, …) are silently skipped rather than
failing the task; images/files/PDF still magic-byte validated and fail-closed.

Adds file-type tests (PDF link, .log octet-stream, unsupported-skip).
isadeks added a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 22, 2026
…+ bundling) (ABCA-745)

PDF attachment screening was silently broken on EVERY channel — live-caught
when the Linear P4 path fetched a PDF and got 'PDF processing is unavailable'.
Two independent bugs, both platform-wide (Jira aws-samples#619 equally affected):

1. API mismatch: code called pdf-parse as the v1 callable `pdfParseFn(buf)`,
   but the pinned pdf-parse@^2.4.5 exposes a `PDFParse` CLASS
   (`new PDFParse({data}).getText()`). A stale `@types/pdf-parse@^1` + a hand-
   written v1 module shim (src/types/pdf-parse.d.ts) made the wrong shape
   compile. Rewrote extractPdfText to the v2 class API (first:N page cap,
   destroy() teardown); removed both v1 type sources so v2's bundled types apply.

2. Bundling: the Linear + Jira webhook processors bundled pdf-parse with esbuild
   (only `externalModules:['@aws-sdk/*']`), mangling its pdfjs/@napi-rs/canvas
   deps → 'DOMMatrix is not defined' at import. Added `nodeModules:['pdf-parse']`
   so it resolves natively at runtime — matching the TaskApi/orchestrator
   attachment-screening bundling that already had the carve-out.

Verified: PDFParse.getText extracts text headless in raw node24 (= the Lambda
runtime). Tests mock the v2 class (ts-jest can't drive pdfjs's ESM worker); the
real extraction is validated on the live deploy. text/csv/etc. unaffected.
isadeks added a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 22, 2026
…kips native canvas (ABCA-745)

After fixing the v2 API + nodeModules bundling, PDF screening still failed live:
pdfjs tried to load the native @napi-rs/canvas binding to supply DOMMatrix/
ImageData/Path2D, but the cross-platform binary isn't bundled for the Lambda's
linux-arm64 (only the build host's darwin binary resolved) → 'Cannot find native
binding' → 'DOMMatrix is not defined' → screening unavailable.

Text extraction never actually uses canvas — pdfjs only touches those globals on
its optional RENDER path. Defining them as inert no-op stubs BEFORE importing
pdf-parse makes pdfjs skip the native-canvas load entirely and extract text
headless — no native binary, host-independent. Verified: getText returns full
text with canvas forced-absent + the three stubs present.

This was platform-wide (confirm-uploads / Jira aws-samples#619 / Linear all fail-closed on
every PDF) — PDF screening has been dead since the pdf-parse v2 bump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(jira): include Jira attachments and recent comments in task context

3 participants